package com.shayu.aicodemother.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import com.mybatisflex.core.query.QueryWrapper;
import com.mybatisflex.spring.service.impl.ServiceImpl;
import com.shayu.aicodemother.ai.AiCodeGenTypeRoutingService;
import com.shayu.aicodemother.ai.AiCodeGenTypeRoutingServiceFactory;
import com.shayu.aicodemother.constant.AppConstant;
import com.shayu.aicodemother.core.AiCodeGeneratorFacade;
import com.shayu.aicodemother.core.builder.VueProjectBuilder;
import com.shayu.aicodemother.core.handler.StreamHandlerExecutor;
import com.shayu.aicodemother.exception.BusinessException;
import com.shayu.aicodemother.exception.ErrorCode;
import com.shayu.aicodemother.exception.ThrowUtils;
import com.shayu.aicodemother.model.dto.app.AppAddRequest;
import com.shayu.aicodemother.model.dto.app.AppQueryRequest;
import com.shayu.aicodemother.model.entity.App;
import com.shayu.aicodemother.mapper.AppMapper;
import com.shayu.aicodemother.model.entity.User;
import com.shayu.aicodemother.model.enums.ChatHistoryMessageTypeEnum;
import com.shayu.aicodemother.model.enums.CodeGenTypeEnum;
import com.shayu.aicodemother.model.vo.AppVO;
import com.shayu.aicodemother.model.vo.UserVO;
import com.shayu.aicodemother.service.AppService;
import com.shayu.aicodemother.service.ChatHistoryService;
import com.shayu.aicodemother.service.ScreenshotService;
import com.shayu.aicodemother.service.UserService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;

import java.io.File;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * 应用 服务层实现。
 *
 * @author <a href="https://gitee.com/tansha11">程序员鲨鱼</a>
 */
@Service
@Slf4j
public class AppServiceImpl extends ServiceImpl<AppMapper, App> implements AppService {


    @Resource
    private UserService userService;

    @Resource
    private AiCodeGeneratorFacade aiCodeGeneratorFacade;

    @Resource
    private ChatHistoryService chatHistoryService;

    @Resource
    private StreamHandlerExecutor streamHandlerExecutor;

    @Resource
    private VueProjectBuilder vueProjectBuilder;

    @Resource
    private ScreenshotService screenshotService;

    @Resource
    private AiCodeGenTypeRoutingServiceFactory aiCodeGenTypeRoutingServiceFactory;

    /**
     * 通过对话生成应用代码
     *
     * @param appId
     * @param message
     * @param loginUser
     * @return
     */
    @Override
    public Flux<String> chatToGenCode(Long appId, String message, User loginUser) {
        //1.参数校验
        ThrowUtils.throwIf(appId == null || appId <= 0, ErrorCode.PARAMS_ERROR, "应用id错误");
        ThrowUtils.throwIf(StrUtil.isBlank(message), ErrorCode.NOT_FOUND_ERROR, "提示词不能为空");

        //2.查看应用信息
        App app = this.getById(appId);
        ThrowUtils.throwIf(app == null, ErrorCode.NOT_FOUND_ERROR, "应用不存在");

        //3.验证用户是否有权限生成代码，只有用户本人能生成代码
        if (!app.getUserId().equals(loginUser.getId())) {
            throw new BusinessException(ErrorCode.NO_AUTH_ERROR, "无权限访问该应用");
        }

        //4.获取到生成代码类型
        String codeGenType = app.getCodeGenType();
        CodeGenTypeEnum codeGenTypeEnum = CodeGenTypeEnum.getEnumByValue(codeGenType);
        if (codeGenTypeEnum == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "无效的代码生成类型");
        }
        //5.保存用户的对话
        chatHistoryService.addChatMessage(appId, message, ChatHistoryMessageTypeEnum.USER.getValue(), loginUser.getId());
        //6.调用ai生成代码(流式)
        Flux<String> codeStream = aiCodeGeneratorFacade.generateAndSaveCodeStream(message, codeGenTypeEnum, appId);
        //7.收集ai响应出来的内容并保存到数据库中
        return streamHandlerExecutor.doExecute(codeStream, chatHistoryService, appId, loginUser, codeGenTypeEnum);
    }


    @Override
    public Long createApp(AppAddRequest appAddRequest, User loginUser) {
        // 参数校验
        String initPrompt = appAddRequest.getInitPrompt();
        ThrowUtils.throwIf(StrUtil.isBlank(initPrompt), ErrorCode.PARAMS_ERROR, "初始化 prompt 不能为空");
        // 构造入库对象
        App app = new App();
        BeanUtil.copyProperties(appAddRequest, app);
        app.setUserId(loginUser.getId());
        // 应用名称暂时为 initPrompt 前 12 位
        app.setAppName(initPrompt.substring(0, Math.min(initPrompt.length(), 12)));
        //创建一个实例
        AiCodeGenTypeRoutingService aiCodeGenTypeRoutingService = aiCodeGenTypeRoutingServiceFactory.createAiCodeGenTypeRoutingService();
        // 使用 AI 智能选择代码生成类型
        CodeGenTypeEnum selectedCodeGenType = aiCodeGenTypeRoutingService.routeCodeGenType(initPrompt);
        app.setCodeGenType(selectedCodeGenType.getValue());
        // 插入数据库
        boolean result = this.save(app);
        ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR);
        log.info("应用创建成功，ID: {}, 类型: {}", app.getId(), selectedCodeGenType.getValue());
        return app.getId();
    }

    /**
     * 根据用户id查询
     *
     * @param app
     * @return
     */
    @Override
    public AppVO getAppVO(App app) {
        if (app == null) {
            return null;
        }
        AppVO appVO = new AppVO();
        if (app.getUserId() != null) {
            User user = userService.getById(app.getUserId());
            UserVO userVo = userService.getUserVo(user);
            appVO.setUser(userVo);
        }
        BeanUtils.copyProperties(app, appVO);
        return appVO;
    }


    /**
     * 创建查询条件
     *
     * @param appQueryRequest
     * @return
     */
    @Override
    public QueryWrapper getQueryWrapper(AppQueryRequest appQueryRequest) {

        if (appQueryRequest == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "请求参数为空");
        }
        Long id = appQueryRequest.getId();
        String appName = appQueryRequest.getAppName();
        String cover = appQueryRequest.getCover();
        String initPrompt = appQueryRequest.getInitPrompt();
        String codeGenType = appQueryRequest.getCodeGenType();
        String deployKey = appQueryRequest.getDeployKey();
        Integer priority = appQueryRequest.getPriority();
        Long userId = appQueryRequest.getUserId();
        String sortField = appQueryRequest.getSortField();
        String sortOrder = appQueryRequest.getSortOrder();
        return QueryWrapper.create()
                .eq("id", id)
                .like("appName", appName)
                .like("cover", cover)
                .like("initPrompt", initPrompt)
                .eq("codeGenType", codeGenType)
                .eq("deployKey", deployKey)
                .eq("priority", priority)
                .eq("userId", userId)
                .orderBy(sortField, "ascend".equals(sortOrder));
    }


    /**
     * 将从数据库中查询到的数据进行脱敏（把数据库层面的 App 转换成前端要用的 AppVO，并且填充用户信息。）
     *
     * @param records
     * @return
     */
    @Override
    public List<AppVO> getAppVOList(List<App> records) {


        if (CollUtil.isEmpty(records)) {
            return new ArrayList<>();
        }
        //将所有的用户id收集起来，防止出现N+1重复查询
        List<Long> listIds = records
                .stream()
                .map(App::getUserId)
                .collect(Collectors.toList());

        //将用户id与UserVo数据一一对应，用map存储起来
        Map<Long, UserVO> userVOMap = userService
                .listByIds(listIds)
                .stream()
                .collect(Collectors.toMap(User::getId, userService::getUserVo));

        //将集合中的APP对象数据转换为AppVO对象
        return records
                .stream()
                .map(app -> {
                            AppVO appVO = getAppVO(app);
                            UserVO userVO = userVOMap.get(app.getUserId());
                            appVO.setUser(userVO);
                            return appVO;
                        }
                )
                .collect(Collectors.toList());
    }


    /**
     * 部署应用
     *
     * @param appId     应用id
     * @param loginUser 登录用户
     * @return
     */
    @Override
    public String deployApp(Long appId, User loginUser) {
        //1.参数校验
        ThrowUtils.throwIf(appId == null || appId <= 0, ErrorCode.PARAMS_ERROR, "应用id错误或者为空");
        ThrowUtils.throwIf(loginUser == null, ErrorCode.NOT_LOGIN_ERROR, "用户未登录");
        //2.查询应用
        App app = this.getById(appId);
        ThrowUtils.throwIf(app == null, ErrorCode.NOT_FOUND_ERROR, "应用不存在");
        //3.认证部署应用的是否用户自身
        if (!app.getUserId().equals(loginUser.getId())) {
            throw new BusinessException(ErrorCode.NO_AUTH_ERROR, "只有用户本人能够部署自己的应用");
        }
        //4.检查是否存在deploykey
        String deployKey = app.getDeployKey();
        //不存在，则随机获取到一个6为的deploykey
        if (StrUtil.isBlank(deployKey)) {
            deployKey = RandomUtil.randomString(6);
        }
        //5.获取到代码生成类型，构建源目录路径
        String codeGenType = app.getCodeGenType();
        String sourceDirName = codeGenType + "_" + appId;
        String sourceDirPath = AppConstant.CODE_OUTPUT_ROOT_DIR + File.separator + sourceDirName;
        //6.检查源目录文件是否存在
        File sourceDir = new File(sourceDirPath);
        if (!sourceDir.exists() || !sourceDir.isDirectory()) {
            throw new BusinessException(ErrorCode.SYSTEM_ERROR, "应用代码不存在，请先生成代码");
        }
        //7.Vue项目特殊处理，需要构建
        CodeGenTypeEnum codeGenTypeEnum = CodeGenTypeEnum.getEnumByValue(codeGenType);
        if (codeGenTypeEnum == (CodeGenTypeEnum.VUE_PROJECT)) {
            //vue项目需要构建
            boolean buildSuccess = vueProjectBuilder.buildProject(sourceDirPath);
            ThrowUtils.throwIf(!buildSuccess, ErrorCode.OPERATION_ERROR, "构建失败，请重试");
            //检查dist目录是否存在
            File distDir = new File(sourceDir, "dist");
            ThrowUtils.throwIf(!distDir.exists(), ErrorCode.OPERATION_ERROR, "构建成功但是dist目录不存在，请重试");
            //将dis目录作为部署目录
            sourceDir = distDir;
            log.info("Vue项目构建成功，将部署dist目录：{}", distDir.getAbsolutePath());
        }
        //8.复制文件到部署目录
        String deployDirPath = AppConstant.CODE_DEPLOY_ROOT_DIR + File.separator + deployKey;
        try {
            FileUtil.copyContent(sourceDir, new File(deployDirPath), true);
        } catch (IORuntimeException e) {
            throw new BusinessException(ErrorCode.SYSTEM_ERROR, "部署失败" + e.getMessage());
        }
        //9.更新app表中deploykey以及部署时间
        App updateApp = new App();
        updateApp.setId(appId);
        updateApp.setDeployKey(deployKey);
        updateApp.setDeployedTime(LocalDateTime.now());
        boolean updateResult = this.updateById(updateApp);
        ThrowUtils.throwIf(!updateResult, ErrorCode.OPERATION_ERROR, "更新失败，请重新部署");
        //10.返回部署文件的可访问url
        String appDeployUrl = String.format("%s/%s/", AppConstant.CODE_DEPLOY_HOST, deployKey);
        //11.异步生成截图并更新应用封面
        generateAppScreenshotAsync(appId, appDeployUrl);
        return appDeployUrl;
    }


    /**
     * 异步生成应用截图并更新封面
     *
     * @param appId        应用id
     * @param appDeployUrl 应用访问URL
     */
    private void generateAppScreenshotAsync(Long appId, String appDeployUrl) {

        //使用虚拟线程异步执行
        Thread.startVirtualThread(() -> {
            //调用截图服务生成截图并上传
            String screenshotUrl = screenshotService.generateAndUploadScreenshot(appDeployUrl);
            //更新应用封面字段
            App app = new App();
            app.setId(appId);
            app.setCover(screenshotUrl);
            boolean updated = this.updateById(app);
            ThrowUtils.throwIf(!updated,ErrorCode.OPERATION_ERROR,"更新应用封面字段失败");
        });
    }


    /**
     * 删除应用同时删除会话记忆
     *
     * @param id
     * @return
     */
    @Override
    public boolean removeById(Serializable id) {
        if (id == null) {
            return false;
        }
        //转化为Long型
        Long appid = (Long) id;
        if (appid <= 0) {
            return false;
        }

        //删除关联的对话历史记录
        try {
            chatHistoryService.deleteByAppId(appid);
        } catch (Exception e) {
            //记录日志,但是不阻止删除
            log.error("删除应用关联会话记忆失败:{}", e.getMessage());
        }
        //删除应用
        return super.removeById(id);
    }
}
